/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#ifndef __MATRIX_H__
#define __MATRIX_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace math {

class Vector3;
class Vector4;
class Quaternion;

/**
 * This is a standard 4x4 matrix.
 * Special functions are included to handle operations in a 3d-environment.
 */
class DLL_EXPORT Matrix4
{
public:
	/** Matrix data */
	float m[16];

	Matrix4();
	Matrix4(float *val);
	Matrix4(float m0, float m1, float m2, float m3, 
		float m4, float m5, float m6, float m7, 
		float m8, float m9, float m10, float m11, 
		float m12, float m13, float m14, float m15);
	Matrix4(const Matrix4 &mat);
	virtual ~Matrix4();

	/**
	 * Access element (z, s)
	 * \param z The row
	 * \param s The column
	 */
	inline float& operator()(int z, int s) 
	{
		return m[s+4*z];
	}

	/** 
	 * Access element (z, s)
	 * \param z The row
	 * \param s The column
	 */
	inline const float& operator()(int z,int s) const
	{
		return m[s+4*z];
	}

	/** 
	 * Access element with index i
	 * \param i The i-th element
	 */
	inline float& operator()(int i)
	{
		return *(&m[0]+i);
	}

	/** Access element (z, s)
	 * @param z The row
	 * @param s The column
	 */
	inline float get(int z,int s) const
	{
		return m[(s-1)+4*(z-1)];
	}

	/** Get the pointer */
	inline operator float*()
	{
		return &m[0];
	}

	/** Get the pointer */
	inline operator float const* () const
	{
		return &m[0];
	}

	/** Transposes the matrix M to M<sup>T</sup>. */
	Matrix4 transpose(void) const;

	void inverseTranspose();

	Matrix4& operator=(const Matrix4 &src);

	/** Matrix equality */
	bool operator==(const Matrix4 &src) const
	{
		for (int i=0;i<16;++i) {
			if ( *(m+i)!=*(src.m+i) )
				return false;
		}
		return true;
	}

	/// Add another matrix to this one and return the result
	inline Matrix4 operator+(const Matrix4 &src) const;

	/// Subtract another matrix from this one and return the result
	inline Matrix4 operator-(const Matrix4 &src) const;

	/// Adds another matrix to this one and sets the result
	inline void    operator+=(const Matrix4 &src);

	/// Subtracts another matrix from this one and sets the result
	inline void    operator-=(const Matrix4 &src);
	Matrix4        operator*(const Matrix4 &src) const;
	Matrix4        operator*(Matrix4 *src);
	void           operator*=(const Matrix4 &src);

	Vector3 operator*(const Vector3 &vec) const;
	Vector4 operator*(const Vector4 &vec) const;

	/**
	 * Add a rotation around the z-axis to the matrix
	 * \param angle The angle of the rotation in degrees
	 */
	void rotateZ(float angle);

	/**
	 * Add a rotation around the y-axis to the matrix
	 * \param angle The angle of the rotation in degrees
	 */
	void rotateY(float angle);

	/**
	 * Add a rotation around the x-axis to the matrix
	 * \param angle The angle of the rotation in degrees
	 */
	void rotateX(float angle);

	/**
	 * Add a rotation to the matrix
	 * \param angle The angle of the rotation in degrees
	 * \param vec A vector around which the rotation is performed.
	 */
	void rotate(float angle, const Vector3 &vec);

	
	/// Add a scale factor	
	void scale(const Vector3 &S);

	/// Set the scale factor
	void setScale(const Vector3 &S);

	/**
	 * Transforms the matrix by a vector
	 * \param C A transformation vector
	 */
	void transform(Vector3 &C) const;

	/// Multiply this matrix A with a matrix B like this: C = B*A
	Matrix4 preMul(const Matrix4 &src) const;

	/// Set the identity matrix
	void setIdentity();

	/// Add a translation vector
	void translate(const Vector3 &T); // {Translate(T);}

	/// Set the translation
	void setTranslation(const Vector3 &T);

	/// Set the translation
	void setTranslation(const float &x, const float &y, const float &z);

	/// Returns the translation component
	math::Vector3 getTranslation() const;

	/// Multiply matrix A with matrix B
	void mul(const Matrix4 &a, const Matrix4 &b);

	void interpolate(const Matrix4 &a, const Matrix4 &b, float t);

	/// Calculates the inverse of this matrix
	bool inv();

	/// Calculates the quaternion of the rotaion
	/// Author: Altjohann
	Quaternion getQuaternion() const;

	/// Calculates the quaternion of the rotaion
	///	Uses the flipped Matrix
	/// Author: Altjohann
	Quaternion getQuaternionFLIPPED() const;

	/// Compares this matrix to matrix A
	bool cmp(const Matrix4 &A) const;

	/// Prints this matrix
	void print() const;
};

/// TODO Implement other stuff
class DLL_EXPORT Matrix3 {
public:
	float m[9];
	Matrix3(const Matrix4 &src);
	Matrix3& operator=(const Matrix4 &src);
};

} // namespace

#endif
